package han.cirno.corrupt.modules.setting

import android.content.Context
import android.content.res.Resources
import android.content.res.XmlResourceParser
import han.cirno.corrupt.modules.setting.SettingConfig.Companion.ANDROID_NAMESPACE
import han.cirno.corrupt.modules.setting.SettingConfig.Companion.APP_NAMESPACE

class SettingConfig(context: Context, id: Int) {
    companion object{
        const val ANDROID_NAMESPACE="http://schemas.android.com/apk/res/android"
        const val APP_NAMESPACE="http://schemas.android.com/apk/res-auto"
    }
    private val itemMapper= HashMap<String,(Resources, XmlResourceParser)->PreferenceItem.Preference>().apply {
        put("Preference") {r,x->PreferenceItem.Preference.Normal(r,x)}
        put("SwitchPreference") {r,x->PreferenceItem.Preference.Switch(r,x)}
        put("DropDownPreference") {r,x->PreferenceItem.Preference.DropDown(r,x)}
        put("SeekBarPreference") {r,x->PreferenceItem.Preference.SeekBar(r,x)}
    }

    var preferenceItems:List<PreferenceItem>
    init {
        val resources = context.resources
        val preferenceItems = mutableListOf<PreferenceItem>()
        resources.getXml(id).use { parser ->
            var currentCategory: PreferenceItem.Category? = null
            var eventType=parser.eventType
            while (eventType!=XmlResourceParser.END_DOCUMENT){
                when(eventType){
                    XmlResourceParser.START_TAG->
                        if(parser.name=="PreferenceCategory") {
                            currentCategory = PreferenceItem.Category(
                                parser.getRawId(resources, "title"))
                        }else {
                            itemMapper[parser.name]?.invoke(resources, parser)?.also { item ->
                                currentCategory?.preferences?.add(item)
                            }
                        }
                    XmlResourceParser.END_TAG ->
                        if (parser.name == "PreferenceCategory" && currentCategory != null) {
                            preferenceItems.add(currentCategory)
                            currentCategory = null
                        }
                }
                eventType = parser.next()
            }
        }
        this.preferenceItems=preferenceItems
    }

    fun findElementByKey(key: String)=
        preferenceItems.findElementByKey(key)

    private fun List<PreferenceItem>.findElementByKey(key:String):PreferenceItem.Preference?{
        forEach {
            if (it is PreferenceItem.Preference&&it.key==key)
                return it
            else if(it is PreferenceItem.Category)
                return it.preferences.findElementByKey(key) ?:return@forEach
        }
        return null
    }

    sealed class PreferenceItem{
        data class Category(
            val title: Int,
            val preferences: MutableList<Preference> =mutableListOf()) : PreferenceItem()
        sealed class Preference(
            val key:String?,
            val title:Int?,
            val summary:Int?,
            val simpleSummary:Boolean,
            val icon:Int?,
            val dependency:String?
        ) : PreferenceItem() {
            constructor(resources: Resources, parser: XmlResourceParser): this(
                parser.getStringNullable(resources, "key"),
                parser.getRawIdNullable(resources, "title"),
                parser.getRawIdNullable(resources,"summary"),
                parser.getBooleanNullable(resources,"useSimpleSummaryProvider")?:false,
                parser.getRawIdNullable(resources,"icon"),
                parser.getStringNullable(resources,"dependency"))
            class Switch(resources: Resources, parser: XmlResourceParser):Preference(resources, parser){
                val defaultValue: Boolean = parser.getBoolean(resources, "defaultValue")
                val summaryOn:Int?=parser.getRawIdNullable(resources,"summaryOn")
                val summaryOff:Int?=parser.getRawIdNullable(resources,"summaryOff")
            }
            class DropDown(resources: Resources, parser: XmlResourceParser): Preference(resources, parser){
                val entries: Array<String> =parser.getStringArray(resources, "entries")
                val entryValues: Array<String> =parser.getStringArray(resources, "entryValues")
                val defaultValue: String?=parser.getStringNullable(resources, "defaultValue")
                operator fun get(value:String):String?{
                    if (entryValues.size!=entries.size)throw IllegalArgumentException("Bad array length")
                    for(i in entries.indices){
                        if (entryValues[i]==value)return entries[i]
                    }
                    return null
                }
            }
            class Normal(resources: Resources,parser: XmlResourceParser):Preference(resources,parser)
            class SeekBar(resources: Resources,parser: XmlResourceParser):Preference(resources,parser){
                val min:Int=parser.getIntNullable(resources,"min")?:0
                val max:Int=parser.getIntNullable(resources,"max")?:0
                val defaultValue: Int = parser.getIntNullable(resources, "defaultValue")?:0

            }
        }
    }
}

private fun <T> XmlResourceParser.getWithResNullable(
    resources: Resources, key: String,
    nonRes: (String) -> T, res: (Resources, Int) -> T):T?{
    val value1=getAttributeValue(APP_NAMESPACE, key)
    val value2=getAttributeValue(ANDROID_NAMESPACE, key)
    val attributeValue = value1?:value2?:return null
    if (attributeValue.startsWith('@'))
        return res(resources,attributeValue.substring(1).toInt())
    return nonRes(attributeValue)
}
private fun <T> XmlResourceParser.getWithRes(
    resources: Resources,key: String,
    nonRes:(String)->T,res:(Resources,Int)->T):T=
    getWithResNullable(resources, key, nonRes, res)
        ?:throw IllegalArgumentException("Not a valid value")
private fun XmlResourceParser.getStringNullable(resources: Resources,key:String):String?=
    getWithResNullable(resources,key,{it},{r,i->r.getString(i)})
private fun XmlResourceParser.getStringArray(resources: Resources,key: String):Array<String> =
    getWithRes(resources,key,{throw IllegalArgumentException("Not an array")},{r,i->r.getStringArray(i)})
private fun XmlResourceParser.getBoolean(resources: Resources,key: String):Boolean=
    getBooleanNullable(resources,key)?:throw IllegalArgumentException("Not a boolean")
private fun XmlResourceParser.getBooleanNullable(resources: Resources,key: String):Boolean?=
    getWithResNullable(resources,key,{it=="true"},{r,i->r.getBoolean(i)})
private fun XmlResourceParser.getRawId(resources: Resources,key: String):Int=
    getRawIdNullable(resources,key)!!
private fun XmlResourceParser.getRawIdNullable(resources: Resources,key: String):Int?=
    getWithResNullable(resources,key,{null},{_,i->i})
private fun XmlResourceParser.getIntNullable(resources: Resources,key: String):Int?=
    getWithResNullable(resources,key,{it.toInt()},{r,i->r.getInteger(i)})
